(function() {
	var DelegateService = function(methodNames) {
		if (methodNames.indexOf('$getByHandle') > -1) {
			throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
		}

		function trueFn() {
			return true;
		}

		return ['$log', function($log) {
			function DelegateInstance(instances, handle) {
				this._instances = instances;
				this.handle = handle;
			}
			methodNames.forEach(function(methodName) {
				DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
			});

			function DelegateService() {
				this._instances = [];
			}
			DelegateService.prototype = DelegateInstance.prototype;
			DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
				var instances = this._instances;
				instance.$$delegateHandle = handle;
				instance.$$filterFn = filterFn || trueFn;
				instances.push(instance);

				return function deregister() {
					var index = instances.indexOf(instance);
					if (index !== -1) {
						instances.splice(index, 1);
					}
				};
			};
			DelegateService.prototype.$getByHandle = function(handle) {
				return new DelegateInstance(this._instances, handle);
			};

			return new DelegateService();

			function instanceMethodCaller(methodName) {
				return function caller() {
					var handle = this.handle;
					var args = arguments;
					var foundInstancesCount = 0;
					var returnValue;

					this._instances.forEach(function(instance) {
						if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
							foundInstancesCount++;
							var ret = instance[methodName].apply(instance, args);
							//Only return the value from the first call
							if (foundInstancesCount === 1) {
								returnValue = ret;
							}
						}
					});

					if (!foundInstancesCount && handle) {
						return $log.warn('Delegate for handle "' + handle + '" could not find a ' + 'corresponding element with delegate-handle="' + handle + '"! ' + methodName + '() was not called!\n' + 'Possible cause: If you are calling ' + methodName + '() immediately, and ' + 'your element with delegate-handle="' + handle + '" is a child of your ' + 'controller, then your element may not be compiled yet. Put a $timeout ' + 'around your call to ' + methodName + '() and try again.');
					}
					return returnValue;
				};
			}

		}];
	}

	var initialize = function(options) {
		var slider = this;

		var touchStartEvent, touchMoveEvent, touchEndEvent;
		if (window.navigator.pointerEnabled) {
			touchStartEvent = 'pointerdown';
			touchMoveEvent = 'pointermove';
			touchEndEvent = 'pointerup';
		} else if (window.navigator.msPointerEnabled) {
			touchStartEvent = 'MSPointerDown';
			touchMoveEvent = 'MSPointerMove';
			touchEndEvent = 'MSPointerUp';
		} else {
			touchStartEvent = 'touchstart';
			touchMoveEvent = 'touchmove';
			touchEndEvent = 'touchend';
		}

		var mouseStartEvent = 'mousedown';
		var mouseMoveEvent = 'mousemove';
		var mouseEndEvent = 'mouseup';

		// utilities
		var noop = function() {}; // simple no operation function
		var offloadFn = function(fn) {
				setTimeout(fn || noop, 0);
			}; // offload a functions execution

		// check browser capabilities
		var browser = {
			addEventListener: !! window.addEventListener,
			transitions: (function(temp) {
				var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
				for (var i in props) if (temp.style[props[i]] !== undefined) return true;
				return false;
			})(document.createElement('swipe'))
		};


		var container = options.el;

		// quit if no root element
		if (!container) return;
		var element = container.children[0];
		var slides, slidePos, width, length;
		options = options || {};
		var index = parseInt(options.startSlide, 10) || 0;
		var speed = options.speed || 300;
		options.continuous = options.continuous !== undefined ? options.continuous : true;

		function setup() {

			// do not setup if the container has no width
			if (!container.offsetWidth) {
				return;
			}

			// cache slides
			slides = element.children;
			length = slides.length;

			// set continuous to false if only one slide
			if (slides.length < 2) options.continuous = false;

			//special case if two slides
			if (browser.transitions && options.continuous && slides.length < 3) {
				element.appendChild(slides[0].cloneNode(true));
				element.appendChild(element.children[1].cloneNode(true));
				slides = element.children;
			}

			// create an array to store current positions of each slide
			slidePos = new Array(slides.length);

			// determine width of each slide
			width = container.offsetWidth || container.getBoundingClientRect().width;

			element.style.width = (slides.length * width) + 'px';

			// stack elements
			var pos = slides.length;
			while (pos--) {

				var slide = slides[pos];

				slide.style.width = width + 'px';
				slide.setAttribute('data-index', pos);

				if (browser.transitions) {
					slide.style.left = (pos * -width) + 'px';
					move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
				}

			}

			// reposition elements before and after index
			if (options.continuous && browser.transitions) {
				move(circle(index - 1), -width, 0);
				move(circle(index + 1), width, 0);
			}

			if (!browser.transitions) element.style.left = (index * -width) + 'px';

			container.style.visibility = 'visible';

			options.slidesChanged && options.slidesChanged();
		}

		function prev(slideSpeed) {

			if (options.continuous) slide(index - 1, slideSpeed);
			else if (index) slide(index - 1, slideSpeed);

		}

		function next(slideSpeed) {

			if (options.continuous) slide(index + 1, slideSpeed);
			else if (index < slides.length - 1) slide(index + 1, slideSpeed);

		}

		function circle(index) {

			// a simple positive modulo using slides.length
			return (slides.length + (index % slides.length)) % slides.length;

		}

		function slide(to, slideSpeed) {

			// do nothing if already on requested slide
			if (index == to) return;

			if (!slides) {
				index = to;
				return;
			}

			if (browser.transitions) {

				var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward

				// get the actual position of the slide
				if (options.continuous) {
					var naturalDirection = direction;
					direction = -slidePos[circle(to)] / width;

					// if going forward but to < index, use to = slides.length + to
					// if going backward but to > index, use to = -slides.length + to
					if (direction !== naturalDirection) to = -direction * slides.length + to;

				}

				var diff = Math.abs(index - to) - 1;

				// move all the slides between index and to in the right direction
				while (diff--) move(circle((to > index ? to : index) - diff - 1), width * direction, 0);

				to = circle(to);

				move(index, width * direction, slideSpeed || speed);
				move(to, 0, slideSpeed || speed);

				if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place

			} else {

				to = circle(to);
				animate(index * -width, to * -width, slideSpeed || speed);
				//no fallback for a circular continuous if the browser does not accept transitions
			}

			index = to;
			offloadFn(options.callback && options.callback(index, slides[index]));
		}

		function move(index, dist, speed) {

			translate(index, dist, speed);
			slidePos[index] = dist;

		}

		function translate(index, dist, speed) {

			var slide = slides[index];
			var style = slide && slide.style;

			if (!style) return;

			style.webkitTransitionDuration = style.MozTransitionDuration = style.msTransitionDuration = style.OTransitionDuration = style.transitionDuration = speed + 'ms';

			style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
			style.msTransform = style.MozTransform = style.OTransform = 'translateX(' + dist + 'px)';

		}

		function animate(from, to, speed) {

			// if not an animation, just reposition
			if (!speed) {

				element.style.left = to + 'px';
				return;

			}

			var start = +new Date();

			var timer = setInterval(function() {

				var timeElap = +new Date() - start;

				if (timeElap > speed) {

					element.style.left = to + 'px';

					if (delay) begin();

					options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

					clearInterval(timer);
					return;

				}

				element.style.left = (((to - from) * (Math.floor((timeElap / speed) * 100) / 100)) + from) + 'px';

			}, 4);

		}

		// setup auto slideshow
		var delay = options.auto || 0;
		var interval;

		function begin() {

			interval = setTimeout(next, delay);

		}

		function stop() {

			delay = options.auto || 0;
			clearTimeout(interval);

		}


		// setup initial vars
		var start = {};
		var delta = {};
		var isScrolling;

		// setup event capturing
		var events = {

			handleEvent: function(event) {
				if (!event.touches && event.pageX && event.pageY) {
					event.touches = [{
						pageX: event.pageX,
						pageY: event.pageY
					}];
				}

				switch (event.type) {
				case touchStartEvent:
					this.start(event);
					break;
				case mouseStartEvent:
					this.start(event);
					break;
				case touchMoveEvent:
					this.touchmove(event);
					break;
				case mouseMoveEvent:
					this.touchmove(event);
					break;
				case touchEndEvent:
					offloadFn(this.end(event));
					break;
				case mouseEndEvent:
					offloadFn(this.end(event));
					break;
				case 'webkitTransitionEnd':
				case 'msTransitionEnd':
				case 'oTransitionEnd':
				case 'otransitionend':
				case 'transitionend':
					offloadFn(this.transitionEnd(event));
					break;
				case 'resize':
					offloadFn(setup);
					break;
				}

				if (options.stopPropagation) event.stopPropagation();

			},
			start: function(event) {

				// prevent to start if there is no valid event
				if (!event.touches) {
					return;
				}

				var touches = event.touches[0];

				// measure start values
				start = {

					// get initial touch coords
					x: touches.pageX,
					y: touches.pageY,

					// store time to determine touch duration
					time: +new Date()

				};

				// used for testing first move event
				isScrolling = undefined;

				// reset delta and end measurements
				delta = {};

				// attach touchmove and touchend listeners
				element.addEventListener(touchMoveEvent, this, false);
				element.addEventListener(mouseMoveEvent, this, false);

				element.addEventListener(touchEndEvent, this, false);
				element.addEventListener(mouseEndEvent, this, false);

				document.addEventListener(touchEndEvent, this, false);
				document.addEventListener(mouseEndEvent, this, false);
			},
			touchmove: function(event) {

				// ensure there is a valid event
				// ensure swiping with one touch and not pinching
				// ensure sliding is enabled
				if (!event.touches || event.touches.length > 1 || event.scale && event.scale !== 1 || slider.slideIsDisabled) {
					return;
				}

				if (options.disableScroll) event.preventDefault();

				var touches = event.touches[0];

				// measure change in x and y
				delta = {
					x: touches.pageX - start.x,
					y: touches.pageY - start.y
				};

				// determine if scrolling test has run - one time test
				if (typeof isScrolling == 'undefined') {
					isScrolling = !! (isScrolling || Math.abs(delta.x) < Math.abs(delta.y));
				}

				// if user is not trying to scroll vertically
				if (!isScrolling) {

					// prevent native scrolling
					event.preventDefault();

					// stop slideshow
					stop();

					// increase resistance if first or last slide
					if (options.continuous) { // we don't add resistance at the end

						translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
						translate(index, delta.x + slidePos[index], 0);
						translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);

					} else {
						// If the slider bounces, do the bounce!
						if (options.bouncing) {
							delta.x = delta.x / ((!index && delta.x > 0 || // if first slide and sliding left
							index == slides.length - 1 && // or if last slide and sliding right
							delta.x < 0 // and if sliding at all
							) ? (Math.abs(delta.x) / width + 1) // determine resistance level
							: 1); // no resistance if false
						} else {
							if (width * index - delta.x < 0) { //We are trying scroll past left boundary
								delta.x = Math.min(delta.x, width * index); //Set delta.x so we don't go past left screen
							}
							if (Math.abs(delta.x) > width * (slides.length - index - 1)) { //We are trying to scroll past right bondary
								delta.x = Math.max(-width * (slides.length - index - 1), delta.x); //Set delta.x so we don't go past right screen
							}
						}

						// translate 1:1
						translate(index - 1, delta.x + slidePos[index - 1], 0);
						translate(index, delta.x + slidePos[index], 0);
						translate(index + 1, delta.x + slidePos[index + 1], 0);
					}

					options.onDrag && options.onDrag();
				}

			},
			end: function() {

				// measure duration
				var duration = +new Date() - start.time;

				// determine if slide attempt triggers next/prev slide
				var isValidSlide = Number(duration) < 250 && // if slide duration is less than 250ms
				Math.abs(delta.x) > 20 || // and if slide amt is greater than 20px
				Math.abs(delta.x) > width / 2; // or if slide amt is greater than half the width

				// determine if slide attempt is past start and end
				var isPastBounds = (!index && delta.x > 0) || // if first slide and slide amt is greater than 0
				(index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0

				if (options.continuous) isPastBounds = false;

				// determine direction of swipe (true:right, false:left)
				var direction = delta.x < 0;

				// if not scrolling vertically
				if (!isScrolling) {

					if (isValidSlide && !isPastBounds) {

						if (direction) {

							if (options.continuous) { // we need to get the next in this direction in place

								move(circle(index - 1), -width, 0);
								move(circle(index + 2), width, 0);

							} else {
								move(index - 1, -width, 0);
							}

							move(index, slidePos[index] - width, speed);
							move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
							index = circle(index + 1);

						} else {
							if (options.continuous) { // we need to get the next in this direction in place

								move(circle(index + 1), width, 0);
								move(circle(index - 2), -width, 0);

							} else {
								move(index + 1, width, 0);
							}

							move(index, slidePos[index] + width, speed);
							move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
							index = circle(index - 1);

						}

						options.callback && options.callback(index, slides[index]);

					} else {

						if (options.continuous) {

							move(circle(index - 1), -width, speed);
							move(index, 0, speed);
							move(circle(index + 1), width, speed);

						} else {

							move(index - 1, -width, speed);
							move(index, 0, speed);
							move(index + 1, width, speed);
						}

					}

				}

				// kill touchmove and touchend event listeners until touchstart called again
				element.removeEventListener(touchMoveEvent, events, false);
				element.removeEventListener(mouseMoveEvent, events, false);

				element.removeEventListener(touchEndEvent, events, false);
				element.removeEventListener(mouseEndEvent, events, false);

				document.removeEventListener(touchEndEvent, events, false);
				document.removeEventListener(mouseEndEvent, events, false);

				options.onDragEnd && options.onDragEnd();
			},
			transitionEnd: function(event) {

				if (parseInt(event.target.getAttribute('data-index'), 10) == index) {

					if (delay) begin();

					options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

				}

			}

		};

		// Public API
		this.update = function() {
			setTimeout(setup);
		};
		this.setup = function() {
			setup();
		};

		this.loop = function(value) {
			if (arguments.length) options.continuous = !! value;
			return options.continuous;
		};

		this.enableSlide = function(shouldEnable) {
			if (arguments.length) {
				this.slideIsDisabled = !shouldEnable;
			}
			return !this.slideIsDisabled;
		};

		this.slide = this.select = function(to, speed) {
			// cancel slideshow
			stop();

			slide(to, speed);
		};

		this.prev = this.previous = function() {
			// cancel slideshow
			stop();

			prev();
		};

		this.next = function() {
			// cancel slideshow
			stop();

			next();
		};

		this.stop = function() {
			// cancel slideshow
			stop();
		};

		this.start = function() {
			begin();
		};

		this.autoPlay = function(newDelay) {
			if (!delay || delay < 0) {
				stop();
			} else {
				delay = newDelay;
				begin();
			}
		};

		this.currentIndex = this.selected = function() {
			// return current index position
			return index;
		};

		this.slidesCount = this.count = function() {
			// return total number of slides
			return length;
		};

		this.kill = function() {
			// cancel slideshow
			stop();

			// reset element
			element.style.width = '';
			element.style.left = '';

			// reset slides so no refs are held on to
			slides && (slides = []);

			// removed event listeners
			if (browser.addEventListener) {

				// remove current event listeners
				element.removeEventListener(touchStartEvent, events, false);
				element.removeEventListener(mouseStartEvent, events, false);
				element.removeEventListener('webkitTransitionEnd', events, false);
				element.removeEventListener('msTransitionEnd', events, false);
				element.removeEventListener('oTransitionEnd', events, false);
				element.removeEventListener('otransitionend', events, false);
				element.removeEventListener('transitionend', events, false);
				window.removeEventListener('resize', events, false);

			} else {

				window.onresize = null;

			}
		};

		this.load = function() {
			// trigger setup
			setup();

			// start auto slideshow if applicable
			if (delay) begin();


			// add event listeners
			if (browser.addEventListener) {

				// set touchstart event on element
				element.addEventListener(touchStartEvent, events, false);
				element.addEventListener(mouseStartEvent, events, false);

				if (browser.transitions) {
					element.addEventListener('webkitTransitionEnd', events, false);
					element.addEventListener('msTransitionEnd', events, false);
					element.addEventListener('oTransitionEnd', events, false);
					element.addEventListener('otransitionend', events, false);
					element.addEventListener('transitionend', events, false);
				}

				// set resize event on window
				window.addEventListener('resize', events, false);

			} else {

				window.onresize = function() {
					setup();
				}; // to play nice with old IE

			}
		};
	}

	// ng-weui-slideBox	
	angular
		.module('ng-weui-slideBox', [])
		.directive('weuiSlideBox', ['$animate', '$timeout', '$compile', '$weuiSlideBoxDelegate', '$weuiScrollDelegate',
		function($animate, $timeout, $compile, $weuiSlideBoxDelegate, $weuiScrollDelegate) {
			return {
				restrict: 'E',
				replace: true,
				transclude: true,
				scope: {
					autoPlay: '=',
					doesContinue: '@',
					slideInterval: '@',
					showPager: '@',
					pagerClick: '&',
					disableScroll: '@',
					onSlideChanged: '&',
					activeSlide: '=?',
					bounce: '@'
				},
				controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
					var _this = this;
					
					var continuous = $scope.$eval($scope.doesContinue) === true;
					var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true
					var shouldAutoPlay = angular.isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
					var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;

					var slider = new initialize({
						el: $element[0],
						auto: slideInterval,
						continuous: continuous,
						startSlide: $scope.activeSlide,
						bouncing: bouncing,
						slidesChanged: function() {
							$scope.currentSlide = slider.currentIndex();

							// Try to trigger a digest
							$timeout(function() {});
						},
						callback: function(slideIndex) {
							$scope.currentSlide = slideIndex;
							$scope.onSlideChanged({ 
								index: $scope.currentSlide, 
								$index: $scope.currentSlide
							});
							$scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
							$scope.activeSlide = slideIndex;
							// Try to trigger a digest
							$timeout(function() {});
						},
						onDrag: function() {
							freezeAllScrolls(true);
						},
						onDragEnd: function() {
							freezeAllScrolls(false);
						}
					});

					function freezeAllScrolls(shouldFreeze) {
						if (shouldFreeze && !_this.isScrollFreeze) {
							$weuiScrollDelegate.freezeAllScrolls(shouldFreeze);
						} else if (!shouldFreeze && _this.isScrollFreeze) {
							$weuiScrollDelegate.freezeAllScrolls(false);
						}
						_this.isScrollFreeze = shouldFreeze;
					}

					slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);

					$scope.$watch('activeSlide', function(nv) {
						if (angular.isDefined(nv)) {
							slider.slide(nv);
						}
					});

					$scope.$on('slideBox.nextSlide', function() {
						slider.next();
					});

					$scope.$on('slideBox.prevSlide', function() {
						slider.prev();
					});

					$scope.$on('slideBox.setSlide', function(e, index) {
						slider.slide(index);
					});

					var deregisterInstance = $weuiSlideBoxDelegate._registerInstance(
						slider, $attrs.delegateHandle, function() {
							return true
						}
					);

					$scope.$on('$destroy', function() {
						deregisterInstance();
						slider.kill();
					});

					this.slidesCount = function() {
						return slider.slidesCount();
					};

					this.onPagerClick = function(index) {
						$scope.pagerClick({index: index});
					};

					$timeout(function() {
						slider.load();
					});
    			}],
				template: 	'<div class="weui-slider">' +
								'<div class="weui-slider-slides" ng-transclude>' +
								'</div>' +
							'</div>',
				link: function($scope, $element, $attr) {
					$animate.enabled($element, false);

					if (!angular.isDefined($attr.showPager)) {
						$scope.showPager = true;
						getPager().toggleClass('hide', !true);
					}

					$attr.$observe('showPager', function(show) {
						if (show === undefined) return;
						show = $scope.$eval(show);
						getPager().toggleClass('hide', !show);
					});

					var pager;

					function getPager() {
						if (!pager) {
							var childScope = $scope.$new();
							pager = angular.element('<weui-pager></weui-pager>');
							$element.append(pager);
							pager = $compile(pager)(childScope);
						}
						return pager;
					}
				}
  			};
		}])
		.directive('weuiSlide', function() {
			return {
				restrict: 'E',
				require: '?^weuiSlideBox',
				compile: function(element) {
					element.addClass('weui-slider-slide');
				}
			};
		})
		.directive('weuiPager', function() {
			return {
				restrict: 'E',
				replace: true,
				require: '^weuiSlideBox',
				template: '<div class="weui-slider-pager">'+
								'<span class="weui-slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)">'+
									'<i class="icon"></i>'+
								'</span>'+
							'</div>',
				link: function($scope, $element, $attr, slideBox) {
					var selectPage = function(index) {
							var children = $element[0].children;
							var length = children.length;
							for (var i = 0; i < length; i++) {
								if (i == index) {
									children[i].classList.add('active');
								} else {
									children[i].classList.remove('active');
								}
							}
						};

					$scope.pagerClick = function(index) {
						slideBox.onPagerClick(index);
					};

					$scope.numSlides = function() {
						return new Array(slideBox.slidesCount());
					};

					$scope.$watch('currentSlide', function(v) {
						selectPage(v);
					});
				}
			};
		})
		.service('$weuiSlideBoxDelegate', DelegateService([
			'update',
			'slide', 
			'select',
			'enableSlide',
			'previous',
			'next',
			'stop', 
			'autoPlay',
			'start',
			'currentIndex', 
			'selected',
			'slidesCount', 
			'count', 
			'loop'
		]))
		.service('$weuiScrollDelegate', DelegateService([
			'resize',
			'scrollTop',
			'scrollBottom',
			'scrollTo',
			'scrollBy',
			'zoomTo',
			'zoomBy',
			'getScrollPosition',
			'anchorScroll',
			'freezeScroll',
			'freezeAllScrolls',
			'getScrollView'
		]));
	
})(); 